﻿namespace MyInstrumentation

open System.Threading
open System.Threading.Tasks

[<assembly: System.Security.SecurityTransparent>]
do ()

module X = 
    let doStuff () = 
        let types = [|  "System.Boolean";
                    "System.Char";
                    "System.Byte";
                    "System.SByte";
                    "System.Int16";
                    "System.UInt16";
                    "System.Int32";
                    "System.UInt32";
                    "System.Int64";
                    "System.UInt64";
                    "System.Double";
                    "System.Single";
                    "System.String";
                    "System.IntPtr";
                    "System.UIntPtr";|]
    //    types |> Seq.map (fun t -> t,t.Replace("System.",""))
    //    |> Seq.map(fun (longf,shortf) -> sprintf "    static let %sType = typedefof<%s>" shortf longf)
    //    |> Seq.iter(printfn "%s")

//        types |> Seq.map (fun t -> t,t.Replace("System.",""))
//        |> Seq.map(fun (longf,shortf) -> sprintf "static member private post%s(v: %s) = ()" shortf longf)
//        |> Seq.iter (printfn "%s")
            
//        types |> Seq.map (fun t -> t,t.Replace("System.",""))
//        |> Seq.map(fun (l,s) -> sprintf "else if (t = %sType) then Logger.post%s(unbox<%s> value)" s s l)
//        |> Seq.iter (printfn "%s")

//        types |> Seq.map (fun t -> t,t.Replace("System.",""))
//        |> Seq.map(fun (l,s) -> sprintf "else if (t = %sType) then Logger.post%s(unbox<%s> value)" s s l)
//        |> Seq.iter (printfn "%s")

//        types |> Seq.map (fun t -> t,t.Replace("System.",""))
//        |> Seq.map(fun (l,s) -> sprintf "else if (typeName = \"%s\") then %sType" l s)
//        |> Seq.iter (printfn "%s")

        types |> Seq.map (fun t -> t,t.Replace("System.",""))
        |> Seq.mapi(fun i (l,s) -> sprintf "| %d -> %sType" i s)
        |> Seq.iter (printfn "%s")


module LogInfo =
    type ParamType = BooleanType | CharType | ByteType | SByteType | Int16Type | UInt16Type | Int32Type | UInt32Type | Int64Type | UInt64Type | DoubleType | SingleType | StringType | IntPtrType | UIntPtrType | ObjectType | SkippedParam | VoidType
    
    type Record = {
        logId: int; //the function is mapped to int
        shortName: string;
        fullName: string;
        expectReturn: bool; //whether the code writes before return
        hasThis: bool;
        parameterTypes: ParamType []
    }

    
    let getStartIndex () = 100
    
    let methodReturnCode = 1
    let methodReturnVoidCode = 2
    let methodThrowExceptionCode = 3



    let typeNameToParamType(maybeTypeName: string option) =
        match maybeTypeName with
         | None -> ParamType.SkippedParam
         | Some typeName -> 
                if (typeName = "System.Boolean") then BooleanType
                else if (typeName = "System.Char") then CharType
                else if (typeName = "System.Byte") then ByteType
                else if (typeName = "System.SByte") then SByteType
                else if (typeName = "System.Int16") then Int16Type
                else if (typeName = "System.UInt16") then UInt16Type
                else if (typeName = "System.Int32") then Int32Type
                else if (typeName = "System.UInt32") then UInt32Type
                else if (typeName = "System.Int64") then Int64Type
                else if (typeName = "System.UInt64") then UInt64Type
                else if (typeName = "System.Double") then DoubleType
                else if (typeName = "System.Single") then SingleType
                else if (typeName = "System.String") then StringType
                else if (typeName = "System.IntPtr") then IntPtrType
                else if (typeName = "System.UIntPtr") then UIntPtrType
                else ObjectType 

    let paramTypeToBytes(lst: System.Collections.Generic.List<byte>) (paramType: ParamType)   = 
        let paramId = 
            match paramType with
                | BooleanType -> 0
                | CharType -> 1
                | ByteType -> 2
                | SByteType -> 3
                | Int16Type -> 4
                | UInt16Type -> 5
                | Int32Type -> 6
                | UInt32Type -> 7
                | Int64Type -> 8
                | UInt64Type -> 9
                | DoubleType -> 10
                | SingleType -> 11
                | StringType -> 12
                | IntPtrType -> 13
                | UIntPtrType -> 14
                | ObjectType -> 15
                | SkippedParam -> 16
                | VoidType -> 17
        lst.AddRange(System.BitConverter.GetBytes(paramId))

    let addString(lst: System.Collections.Generic.List<byte>) (s: string) = 
        let bytes = System.Text.Encoding.UTF8.GetBytes(s)
        lst.AddRange(System.BitConverter.GetBytes(bytes.Length))
        lst.AddRange(bytes)
        
    let recordToBytes (lst: System.Collections.Generic.List<byte>) (record: Record) = 
        lst.AddRange(System.BitConverter.GetBytes(record.logId))
        addString lst (record.shortName)
        addString lst (record.fullName)
        lst.AddRange(System.BitConverter.GetBytes(record.expectReturn))
        lst.AddRange(System.BitConverter.GetBytes(record.hasThis))
        lst.AddRange(System.BitConverter.GetBytes(record.parameterTypes.Length))
        record.parameterTypes |> Seq.iter (paramTypeToBytes lst)
     
    let incBy (x: int ref) (v: int) = x := !x + v

    let incByAndReturn (x: int ref) (v: int) (result:'a) = 
        incBy x v
        result

    let readInt (buffer: byte[]) (x: int ref) = 
        System.BitConverter.ToInt32(buffer, !x)
        |> incByAndReturn x 4

    let readString (buffer: byte[]) (x: int ref) =
        let numBytes = readInt buffer x
        System.Text.Encoding.UTF8.GetString(buffer, !x, numBytes)
        |> incByAndReturn x numBytes 

    let readBool (buffer: byte[]) (x: int ref) = 
        System.BitConverter.ToBoolean(buffer, !x)
        |> incByAndReturn x 1
    
    let bytesToParam(buffer: byte[]) (x: int ref): ParamType = 
        let intValue = readInt buffer x
        match intValue with
            | 0 -> BooleanType
            | 1 -> CharType
            | 2 -> ByteType
            | 3 -> SByteType
            | 4 -> Int16Type
            | 5 -> UInt16Type
            | 6 -> Int32Type
            | 7 -> UInt32Type
            | 8 -> Int64Type
            | 9 -> UInt64Type
            | 10 -> DoubleType
            | 11 -> SingleType
            | 12 -> StringType
            | 13 -> IntPtrType
            | 14 -> UIntPtrType
            | 15 -> ObjectType
            | 16 -> SkippedParam
            | 17 -> VoidType
            | _ -> failwith "Unknown value in bytesToParam"


    let bytesToRecord (buffer: byte[]) (i: int ref): Record =
        let logId = readInt buffer i 
        let shortName = readString buffer i
        let fullName = readString buffer i
        let expectReturn = readBool buffer i
        let hasThis = readBool buffer i
        let len = readInt buffer i
        let parameters = 
                 [|for j in 0..(len - 1) do
                        yield (bytesToParam buffer i)
                 |]
        { logId = logId; 
          shortName = shortName; 
          fullName = fullName; 
          expectReturn = expectReturn; 
          hasThis = hasThis;
          parameterTypes = parameters
        }                 


//        lst.AddRange(System.BitConverter.GetBytes(record.logId))
//        addString lst (record.shortName)
//        addString lst (record.fullName)
//        lst.AddRange(System.BitConverter.GetBytes(record.expectReturn))
//        lst.AddRange(System.BitConverter.GetBytes(record.hasThis))
//        lst.AddRange(System.BitConverter.GetBytes(record.parameterTypes.Length))
//        record.parameterTypes |> Seq.iter (paramTypeToBytes lst)
        
    let bytesToRecords (buffer: byte[]): Record[] =
        let len = Array.length buffer
        let i = ref 0
        [| while (!i < len) do
              yield bytesToRecord buffer i
        |]

        
    let recordsToBytes(records: seq<Record>) (lst: System.Collections.Generic.List<byte>) =
        records
        |> Seq.iter(recordToBytes lst)

    let recordsToFile (filename: string) (records: seq<Record>) = 
        let buffer = System.Collections.Generic.List<byte>()
        recordsToBytes records buffer 
        printfn "Size of records %d; number of records %d" (buffer.Count) (Seq.length records)
        System.IO.File.WriteAllBytes(filename, buffer.ToArray())

    let recordsFromFile (filename: string): Record[] = 
        let buffer = System.IO.File.ReadAllBytes(filename)
        bytesToRecords buffer

    type CallRecord = {
        funcId: int;
        paramValues: string[]; // to do
        returnValue: string;
    }

    let readMethodId (bytes: byte[]) (x: int ref) = 
        readInt bytes x

    let readObject (buffer: byte[]) (x: int ref) = 
        let flag = true //readBool buffer x
        if flag then
            let t = readString buffer x
            let v = readString buffer x
            t + ": " + v
        else
            "<null>"
            //readInt buffer x |> sprintf "%d"


    let readFuncParam (param: ParamType) (buffer: byte[]) (x: int ref) = 
        

        match param with
            | ObjectType -> readObject buffer x
            | SkippedParam -> "skipped"
            | VoidType -> printfn "Void"; 
                          "void"
                          readObject buffer x
            | _ -> failwith "na"

    type CallTree = Tree of CallRecord*(CallTree []) | Return of int*int | Done
    type FuncInfo = System.Collections.Generic.Dictionary<int, Record> 

    let rec readRecord (funcInfo: FuncInfo) (bytes: byte[]) (x: int ref) (dummy: string)= 
        let test = ref (bytes.Length - 4)
        let dummy = readInt bytes test
        let funcId = readMethodId bytes x
        if (funcId >= 100) then // to do

            let record = funcInfo.[funcId]
            printfn "%s\n" record.fullName


            let mutable i = 0
    //        if record.hasThis then
    //            let value = readFuncParam (record.parameterTypes.[i]) bytes x 
    //            i <- i + 1
            let allButLast (x: 'a[]) = 
                Array.sub x 0 (Array.length x - 1)
            
            let last(x: 'a[]) = 
                x.[Array.length x - 1]
                    
            let paramValues = 
                [|
                    for p in allButLast(record.parameterTypes) do
                        let value = readFuncParam p bytes x
                        printfn "           %s" value
                        yield value
                |]

            if (record.fullName.Contains("main@")) then
                printfn "x"
            //let interpretation of parameters depends on hasThis and hasReturn
            let saveRetCode = ref 0
            let children = 
                    [|
                        let flag = ref true
                        while (!flag) do
                            let nextRecord = readRecord funcInfo bytes x record.fullName
                            if (record.fullName.Contains("main@")) then
                                printfn "x"
                            match nextRecord with
                             | Return (retCode,returnedFuncId) -> flag := false
                                                                  if (returnedFuncId <> funcId) then
                                                                    failwith "returned func id is expected to be different"
                                                                  saveRetCode := retCode
                             | _ -> yield nextRecord 
                    |] 
            //let returnCode = readInt bytes x
            //depends on return code, void, throw etc.
            let returnValue =
                //if (!saveRetCode <> methodReturnVoidCode) then
                    readFuncParam (last record.parameterTypes) bytes x
//                else
//                    "void"

            let callRecord = {funcId = funcId; paramValues = paramValues; returnValue = returnValue}

            Tree (callRecord, children)
        else
            printfn "Return %d" funcId
            let returnedId = readInt bytes x
            Return (funcId,returnedId) //to do:

    let dumpCalls (fileName: string) =
        let bytes = System.IO.File.ReadAllBytes(fileName)
        let mutable i = 0
        while (i < bytes.Length) do
            let f = System.BitConverter.ToBoolean(bytes, i)
            i <- i + 1
            if not f then
               i <- i + 1
               let x = System.BitConverter.ToInt32(bytes, i)
               i <- i + 4
               let s = System.Text.Encoding.UTF8.GetString(bytes, i,x)
               i <- i + x
               printf "%s\t" s
            else
               let x = System.BitConverter.ToInt32(bytes, i)
               i <- i + 4
               printf "%d\t" x
//        Logger.postByte(1uy)
//        Logger.postBytes(System.BitConverter.GetBytes(bytes.Length))
//        //Logger.postInt32(bytes.Length)
//        Logger.postBytes(bytes)

    let readCalls (funcInfo: FuncInfo) (fileName: string): CallTree = 
        let bytes = System.IO.File.ReadAllBytes(fileName)
        readRecord funcInfo bytes (ref 0) ""
    
    let dumpCallTree (funcInfo: FuncInfo) (callTree: CallTree) =
        let rec loop (padding: string) (callTree: CallTree) =
            match callTree with
             | Tree (record, children) ->
                     printfn "%s%s" padding funcInfo.[record.funcId].fullName
                     for child in children do
                        loop (padding + " ") child               
             | _ -> ()
        loop "" callTree
            
    let copyCallTree (callTree: CallTree) 
                         (makeNode: CallRecord -> 'a[] -> 'a)
                         
                          =
        let rec loop (callTree: CallTree) =
            match callTree with
             | Tree (record, children) ->
                     //printfn "%s%s" padding funcInfo.[record.funcId].fullName
                     let newChildren =
                         [|for child in children do
                            yield loop child|]      
                     makeNode record newChildren     
             | _ -> failwith "na"
        loop callTree
type Buffer(id: int) = 
    let lst = System.Collections.Generic.List<byte>()
    member this.postByte(b: byte) = lst.Add(b)
    member this.postBytes(bs: byte[]) = lst.AddRange(bs)
    interface System.IDisposable with
        override this.Dispose() = 
            printfn "ThreadId %d" id
            lst |> Seq.iter (printf "%d")
            printfn ""
            let fn = sprintf @"D:\DotNetTracerTmp\buffer_%d" id
            System.IO.File.WriteAllBytes(fn, lst.ToArray())
            printfn "Finished writing %id" id
            
            ()


type WrappedStorage() = class
    static  let stack = System.Collections.Concurrent.ConcurrentStack<_>()
    static let storage = new ThreadLocal<Buffer>(fun _ -> 
                                                    let threadId = Thread.CurrentThread.ManagedThreadId
                                                    let buffer = new Buffer(threadId)
                                                    stack.Push(buffer)
                                                    buffer)
    member public this.GetBuffer() = 
        storage.Value

    interface System.IDisposable with
        override this.Dispose() = 
            while(stack.Count > 0) do
                let ok,v = stack.TryPop()
                if ok then
                    let d = (v :> System.IDisposable)
                    
                    d.Dispose()
                    
end



type Logger() = class

//    static let storage = new ThreadLocal<Buffer>(fun _ -> 
//                                                    let threadId = Thread.CurrentThread.ManagedThreadId
//                                                    Buffer(threadId))
    static let storage = new WrappedStorage() 


    static let BooleanType = typedefof<System.Boolean>
    static let CharType = typedefof<System.Char>
    static let ByteType = typedefof<System.Byte>
    static let SByteType = typedefof<System.SByte>
    static let Int16Type = typedefof<System.Int16>
    static let UInt16Type = typedefof<System.UInt16>
    static let Int32Type = typedefof<System.Int32>
    static let UInt32Type = typedefof<System.UInt32>
    static let Int64Type = typedefof<System.Int64>
    static let UInt64Type = typedefof<System.UInt64>
    static let DoubleType = typedefof<System.Double>
    static let SingleType = typedefof<System.Single>
    static let StringType = typedefof<System.String>
    static let IntPtrType = typedefof<System.IntPtr>
    static let UIntPtrType = typedefof<System.UIntPtr>

    static member private postBytes(bs: byte[]) = storage.GetBuffer().postBytes(bs) 
    static member private postByte(b: System.Byte) = storage.GetBuffer().postByte(b)

    static member private postBoolean(v: System.Boolean) = 
        if v then Logger.postByte(1uy) 
        else Logger.postByte(0uy)

    static member private postChar(v: System.Char) =
        Logger.postBytes(System.BitConverter.GetBytes(v))

    static member private postSByte(v: System.SByte) = 
        Logger.postByte((byte)v)

    static member private postInt16(v: System.Int16) = 
        Logger.postBytes(System.BitConverter.GetBytes(v))

    static member private postUInt16(v: System.UInt16) = 
        Logger.postBytes(System.BitConverter.GetBytes(v))

    static member private postInt32(v: System.Int32) = 
        //Logger.postByte(1uy)
        Logger.postBytes(System.BitConverter.GetBytes(v))

    static member private postUInt32(v: System.UInt32) = 
        
        Logger.postBytes(System.BitConverter.GetBytes(v))

    static member private postInt64(v: System.Int64) = 
        Logger.postBytes(System.BitConverter.GetBytes(v))
    
    static member private postUInt64(v: System.UInt64) = 
        Logger.postBytes(System.BitConverter.GetBytes(v))

    static member private postDouble(v: System.Double) = 
        Logger.postBytes(System.BitConverter.GetBytes(v))

    static member private postSingle(v: System.Single) = 
        Logger.postBytes(System.BitConverter.GetBytes(v))

    static member private postString(v: System.String) = 
        let bytes = System.Text.Encoding.UTF8.GetBytes(v)
//        Logger.postByte(1uy)
//        Logger.postBytes(System.BitConverter.GetBytes(bytes.Length))
//        Logger.postByte(0uy)
        Logger.postInt32(bytes.Length)
        Logger.postBytes(bytes)

    static member private postIntPtr(v: System.IntPtr) = 
        Logger.postBytes(System.BitConverter.GetBytes(v.ToInt64()))

    static member private postUIntPtr(v: System.UIntPtr) = 
        Logger.postBytes(System.BitConverter.GetBytes(v.ToUInt64()))
    
    static member private postObject(value: System.Object) = 
        let hashCode = System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(value)
        Logger.postInt32(hashCode)

    static member private postNull() = 
        Logger.postByte(0uy)

    static member private PostType(t: System.Type) = 
        //t.Module.MetadataToken
        Logger.postString(t.Name) //to do: map types to id during assembly instrumentation
                                  //or figure out a way with method t.MetadataToken
        //to do: before start enumerate all the types and dump the type handles, record the type handles only
        //t.TypeHandle. 
        //TypeHandle encapsulates a pointer to an internal data structure that represents the type. This handle is unique during the process lifetime. The handle is valid only in the application domain in which it was obtained.
        //to do: app domains???
    static member getStorage() = storage

    static member beginMethod (nameId:int) (name: string) = 
        Logger.postInt32(nameId)

    
    static member postObjectParam (value: obj) =
        if value <> null then
            //Logger.postByte(1uy)
            let t = value.GetType()
            Logger.PostType(t)
//            if      (t = BooleanType)   then Logger.postBoolean(unbox<System.Boolean> value)
//            else if (t = CharType)      then Logger.postChar(unbox<System.Char> value)
//            else if (t = ByteType)      then Logger.postByte(unbox<System.Byte> value)
//            else if (t = SByteType)     then Logger.postSByte(unbox<System.SByte> value)
//            else if (t = Int16Type)     then Logger.postInt16(unbox<System.Int16> value)
//            else if (t = UInt16Type)    then Logger.postUInt16(unbox<System.UInt16> value)
//            else if (t = Int32Type)     then Logger.postInt32(unbox<System.Int32> value)
//            else if (t = UInt32Type)    then Logger.postUInt32(unbox<System.UInt32> value)
//            else if (t = Int64Type)     then Logger.postInt64(unbox<System.Int64> value)
//            else if (t = UInt64Type)    then Logger.postUInt64(unbox<System.UInt64> value)
//            else if (t = DoubleType)    then Logger.postDouble(unbox<System.Double> value)
//            else if (t = SingleType)    then Logger.postSingle(unbox<System.Single> value)
//            else if (t = StringType)    then Logger.postString(unbox<System.String> value)
//            else if (t = IntPtrType)    then Logger.postIntPtr(unbox<System.IntPtr> value)
//            else if (t = UIntPtrType)   then Logger.postUIntPtr(unbox<System.UIntPtr> value)
//            else Logger.postObject(value)
              // to do:
            let strValue = 
                if      (t = BooleanType)   then value.ToString()
                else if (t = CharType)      then value.ToString()
                else if (t = ByteType)      then value.ToString()
                else if (t = SByteType)     then value.ToString()
                else if (t = Int16Type)     then value.ToString()
                else if (t = UInt16Type)    then value.ToString()
                else if (t = Int32Type)     then value.ToString()
                else if (t = UInt32Type)    then value.ToString()
                else if (t = Int64Type)     then value.ToString()
                else if (t = UInt64Type)    then value.ToString()
                else if (t = DoubleType)    then value.ToString()
                else if (t = SingleType)    then value.ToString()
                else if (t = StringType)    then value.ToString()
                else if (t = IntPtrType)    then value.ToString()
                else if (t = UIntPtrType)   then value.ToString()         
                else System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(value).ToString()
            Logger.postString strValue
        else
            //Logger.postNull()
            //Logger.postByte(1uy)
            
            Logger.postString "nullType"
            Logger.postString "null"

    static member endMethodParams () = 
        () //ignore this, info about number and type of parameters should be in the method type

    static member methodReturn (value: obj) (funcId: int)=
        printfn "Write return: %d" funcId
        Logger.postInt32(LogInfo.methodReturnCode)
        Logger.postInt32(funcId)
        Logger.postObjectParam(value)

    static member methodReturnVoid (funcId: int) =
        printfn "Write void: %d" funcId
        Logger.postInt32(LogInfo.methodReturnVoidCode) 
        Logger.postInt32(funcId)
        
        Logger.postObjectParam("void")

    static member methodThrow (except: System.Exception) (funcId: int) = 
        Logger.postInt32(LogInfo.methodThrowExceptionCode) 
        Logger.postInt32(funcId)
        Logger.postObjectParam(box except)

    static member disposeAll () =
        (Logger.getStorage() :> System.IDisposable).Dispose()

end
